/* Copyright (c) 2007-2008, Jeffrey R Griffin
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of Jeffrey R Griffin nor the
*       names of his contributors may be used to endorse or promote products
*       derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY Jeffrey R Griffin ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL <copyright holder> BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package net.visibleblue.android.paranoid;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.security.NoSuchAlgorithmException;

import org.apache.http.entity.ContentProducer;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.IContentProvider;
import android.content.Intent;
import android.database.Cursor;
import android.net.ContentURI;
import android.os.Bundle;
import android.provider.BaseColumns;

public class ParanoidIntentReceiver extends android.content.IntentReceiver {
	
	@Override
	public void onReceiveIntent(Context context, Intent intent) {
		Bundle dataBundle = intent.getExtras();
		int contactId = 0;
		if(dataBundle.getString(Extras.CONTACT_SELECTION_ROW)!=null)
		{
			String row = dataBundle.getString(Extras.CONTACT_SELECTION_ROW);
			String literal = dataBundle.getString(Extras.CONTACT_SELECTION_LITERAL);
			contactId = queryForContactID(context, row, literal);
		}
		String algorithm = dataBundle.getString(Extras.PUBLIC_KEY_ALGORITHM);
		String digest = dataBundle.getString(Extras.DIGEST);
		String keyURL = dataBundle.getString(Extras.PUBLIC_KEY_FILE_URL);
		
		try
		{
			if(intent.getAction().equals(Actions.INSERT))
				insert(context, digest, contactId, algorithm, keyURL);
			else if(intent.getAction().equals(Actions.UPDATE))
				update(context, digest, contactId, algorithm, keyURL);
		}
		catch(IOException ioEx)
		{
			throw new RuntimeException(ioEx);
		}
		catch(NoSuchAlgorithmException nsaEx)
		{
			throw new RuntimeException(nsaEx);
		}
	};
	
	private static int queryForContactID(Context context, String selectionRow, 
			String selectionLiteral)
	{
		String[] projection = new String[] {
			    BaseColumns._ID,
			    selectionRow
			};
		String selection = selectionRow+" = \""+selectionLiteral+"\"";
		Cursor cursor = context.getContentResolver().query(
				android.provider.Contacts.People.CONTENT_URI, projection, selection, null, 
				android.provider.Contacts.People.DEFAULT_SORT_ORDER);
		if(cursor.count()==0)
			return 0;
		cursor.moveTo(0);
		return cursor.getInt(cursor.getColumnIndex(BaseColumns._ID));
	}
	
	//digest must pre-exist
	private static void update(Context context, String digest, int contactId, String algorithm, 
			String keyURL) throws IOException, NoSuchAlgorithmException
	{
		if(keyURL==null && digest==null)
			return;
		byte[] buffer = null;
		if(digest==null)
		{
			buffer = getData(new URL(keyURL), algorithm);
			digest = Alice.getMD5Digest64(buffer);
		}
		Cursor cursor = queryByDigest(context, digest);
		if(cursor.count()==0)
			return;
		cursor.moveTo(0);
		if(contactId!=0)
			cursor.updateInt(cursor.getColumnIndex(Provider.CONTACT_ID_COLUMN), contactId);
		if(algorithm!=null)
		{
			cursor.updateString(cursor.getColumnIndex(Provider.PUBLIC_KEY_ALGORITHM_COLUMN), 
					algorithm);
		}
		if(buffer!=null)
			writeData(context, cursor, buffer);
		cursor.commitUpdates();
		cursor.deactivate();
	}
	
	//digest may pre-exist
	private static void insert(Context context, String digest, int contactId, 
			String algorithm, String keyURL) throws IOException, NoSuchAlgorithmException
	{
		byte[] buffer = null;
		if(keyURL!=null)
		{ 
			buffer = getData(new URL(keyURL), algorithm);
			if(!Alice.verifyMD5KeyDigest(digest, buffer))
				return;
		}
		Cursor cursor = queryByDigest(context, digest);
		if(cursor.count()==0)
		{
			ContentURI entryURI = context.getContentResolver().insert(Provider.CONTENT_URI, 
					getValues(digest, contactId, algorithm));
			cursor = context.getContentResolver().query(entryURI, null, null, null, 
					Provider.DEFAULT_SORT_ORDER);
			cursor.moveTo(0);
		}
		else
		{
			cursor.moveTo(0);
			if(contactId!=0)
				cursor.updateInt(cursor.getColumnIndex(Provider.CONTACT_ID_COLUMN), contactId);
			if(algorithm!=null)
			{
				cursor.updateString(cursor.getColumnIndex(Provider.PUBLIC_KEY_ALGORITHM_COLUMN), 
						algorithm);
			}
			cursor.commitUpdates();
		}
		if(buffer!=null)
		{
			cursor.requery();
			cursor.first();
			writeData(context, cursor, buffer);
			cursor.commitUpdates();
		}
		cursor.deactivate();
	}
	
	private static ContentValues getValues(String digest, int contactId, String algorithm)
	{
		ContentValues values = new ContentValues();
		if(digest!=null)
			values.put(Provider.DIGEST_COLUMN, digest);
		if(contactId!=0)
			values.put(Provider.CONTACT_ID_COLUMN, new Integer(contactId));
		if(algorithm!=null)
			values.put(Provider.PUBLIC_KEY_ALGORITHM_COLUMN, algorithm);
		return values;
	}
	
	private static Cursor queryByDigest(Context context, String digest)
	{
		String[] projection = new String[] {
			    BaseColumns._ID,
			    Provider.CONTACT_ID_COLUMN,
			    Provider.DIGEST_COLUMN,
			    Provider.PUBLIC_KEY_ALGORITHM_COLUMN,
			    Provider.DATA_COLUMN
			};
		String selection = Provider.DIGEST_COLUMN+" = '"+digest+"'";
		return context.getContentResolver().query(Provider.CONTENT_URI, projection, 
					selection, null, Provider.DEFAULT_SORT_ORDER);
	}
	
	private static byte[] getData(URL fileUrl, String algorithm) 
		throws IOException, NoSuchAlgorithmException
	{
		URLConnection conn = fileUrl.openConnection();
		conn.connect();
		byte[] buffer = new byte[conn.getContentLength()];
		InputStream in = conn.getInputStream();
		in.read(buffer);
		return Alice.getPublicKey(buffer, algorithm).getEncoded();
	}
	
	private static void writeData(Context context, Cursor cursor, byte[] data) 
	{
		int id = cursor.getInt(cursor.getColumnIndex(BaseColumns._ID));
		String path = id+".dat";
		try
		{
			FileOutputStream fout = context.openFileOutput(id+".dat", Context.MODE_WORLD_READABLE);
			fout.write(data);
			fout.flush();
			fout.close();
			String fullPath = context.getFileStreamPath(path).getAbsolutePath();
			cursor.updateString(cursor.getColumnIndex(Provider.DATA_COLUMN), fullPath);
		}
		catch(FileNotFoundException fnfEx)
		{
			throw new RuntimeException(fnfEx);
		}
		catch(IOException ioEx)
		{
			throw new RuntimeException(ioEx);
		}
	}

}
